# -*- coding: utf-8 -*-

# $Id: command.py 11 2012-09-03 00:27:38Z alexis.roda.villalonga@gmail.com $

"""This modules provides functionality shared between the ``dja_skel``
script and the ``skel`` management command.
"""

import locale
from optparse import make_option
import sys
import textwrap

from pkg_resources import iter_entry_points

from dja_skel.lib import errors
from dja_skel.lib.skeleton import Skeleton


def get_option_list():
    """Returns a list of command line options.
    """
    return (
        # commands
        make_option("--describe",
                    action="store_const",
                    const="cmd_describe_template",
                    dest="cmd",
                    default=None,
                    help="Describe a template in depth."
                    ),
        make_option("-l", "--list-templates",
                    action="store_const",
                    const="cmd_list_templates",
                    dest="cmd",
                    default=None,
                    help="Display a list of available templates."
                    ),
        make_option("--list-variables",
                    action="store_const",
                    const="cmd_list_variables",
                    dest="cmd",
                    default=None,
                    help="List template variables."
                    ),
        make_option("-i", "--instantiate",
                    action="store_const",
                    const="cmd_instantiate",
                    dest="cmd",
                    default=None,
                    help="Instantiate a template."
                    ),
        # options
        make_option("-t", "--template",
                    action="store",
                    default=None,
                    dest="template",
                    help="Specifies a template name."
                    ),
        make_option("--encoding",
                    action="store",
                    default=locale.getpreferredencoding().lower(),
                    dest="encoding",
                    help="Encoding used for output files."
                    ),
        )


class SkelCommand(object):
    """The skel command.

    This class implements the logic behind the ``dja_skel`` script and
    the ``skel`` management command. It **must** be subclassed and the
    :meth:`get_root` overriden.
    """

    entry_point_group = None

    def __init__(self, args, options):
        """Constructor.

        :param args: a tuple with the command line arguments
        :param options: a dictionary with the command line options
        """
        self._args, self._vars = self._parse_args(args)
        self._options = options
        self.stdout = None
        self.stderr = None
        self.set_output_streams(sys.stdout, sys.stderr)

    def set_output_streams(self, stdout, stderr):
        """Configures the ``stdout`` and ``stderr`` streams. The
        objects passed as arguments must define a ``write()`` method.

        By default ``sys.stdout`` and ``sys.stderr`` are used.
        """
        self.stdout = stdout
        self.stderr = stderr

    def run(self):
        """Runs the command.
        """
        cmd = self._options["cmd"]
        if cmd is None:
            self.error("An action must be specified.")
        cmd = getattr(self, cmd)
        try:
            cmd()
        except errors.SkelError as excpt:
            self.error(str(excpt))

    def get_root(self):
        """Return the path of the root directory where the result of
        the instantiation will be stored.

        Subclasses **must** override this method.
        """
        raise NotImplementedError()

    def error(self, message, retcode=1):
        """This method displays an error message and ends the process
        with the ``retcode`` exit code.

        Subclasses may override this method in order to use a
        different error reporting schema. Whatever the implementation
        is it **must** abort the execution.
        """
        self.stderr.write(u"Error: %s" % message)
        if not message.endswith("\n"):
            self.stderr.write("\n")
        sys.exit(retcode)

    @staticmethod
    def _parse_args(args):
        """Parses the non-option command line arguments.

        Returns a tuple with the arguments and a dictinary with the
        variables.
        """
        arg_vars = {}
        real_args = []
        for arg in args:
            if "=" in arg:
                name, value = arg.split("=", 1)
                arg_vars[name] = value
            else:
                real_args.append(arg)
        return (real_args, arg_vars)

    def cmd_list_templates(self):
        """Display a list of the available templates.
        """
        self.stdout.write("List of available skeletons:\n")
        templates = []
        for entry_point in iter_entry_points(self.entry_point_group):
            skeleton = entry_point.load()
            templates.append((entry_point.name, skeleton.summary))
        templates.sort()
        max_name_len = max( (len(n) for n, s in templates) )
        for name, summary in templates:
            self.stdout.write("* {}: {}\n".format(
                name.ljust(max_name_len),
                summary
                ))

    def cmd_list_variables(self):
        """Display the list of variables required by a given template.
        """
        template = self._options["template"]
        if template is None:
            self.error("A template is required, use --template.")
        counter = 0
        for entry_point in iter_entry_points(self.entry_point_group, name=template):
            counter += 1
            skeleton = entry_point.load()
            self.stdout.write("List of variables '{}':\n".format(entry_point.name))
            for var in skeleton.variables:
                self.stdout.write("  {}\n".format(var))
        if not counter:
            self.error("Template '%s' not found." % template)

    def cmd_instantiate(self):
        """Instantiates the template.
        """
        template = self._options["template"]
        encoding = self._options["encoding"]
        if template is None:
            self.error("A template is required, use --template.")
        root = self.get_root()
        counter = 0
        for entry_point in iter_entry_points(self.entry_point_group, name=template):
            counter += 1
            skeleton_class = entry_point.load()
            skeleton = skeleton_class()
            skeleton.instantiate(root, self._vars, encoding)
        if not counter:
            self.error("Template '%s' not found." % template)

    def cmd_describe_template(self):
        """Describes a template.
        """
        template = self._options["template"]
        if template is None:
            self.error("A template is required, use --template.")
        counter = 0
        for entry_point in iter_entry_points(self.entry_point_group, name=template):
            counter += 1
            skeleton_class = entry_point.load()
            describe_template(skeleton_class, self.stdout.write)


def describe_template(class_, writer):
    if issubclass(class_, Skeleton):
        name = getattr(class_, "name", None)
        if name is None:
            name = class_.__name__
        description = getattr(class_, "description", "Not documented.\n")
        writer("Template: %s\n" % name)
        writer("==========%s\n" % ("=" * len(name)))
        writer("\n")
        first, rest = description.split("\n", 1)
        description = "%s\n%s" % (first, textwrap.dedent(rest))
        writer("%s\n" % description)
        writer("\n")
        writer("Variables: %s\n" % ", ".join(class_.variables))
        writer("\n")

        found = False
        for base in class_.__bases__:
            if issubclass(base, Skeleton) and not found:
                writer("Templates %s inherits from\n" % name)
                writer("========================%s\n" % ("=" * len(name)))
                found = True
            describe_template(base, writer)

        if class_.base_skeletons:
            writer("Templates %s is based on\n" % name)
            writer("======================%s\n" % ("=" * len(name)))
            for base in class_.base_skeletons:
                describe_template(base, writer)
